/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package miner

import (
	"context"
	"sync"

	commconfig "hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/keystore"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/consensus/common"
	"hundsun.com/hsl/hschain/consensus/types"
	"hundsun.com/hsl/hschain/module/common/utils"
	"hundsun.com/hsl/hschain/protos"
)

var (
	configParamMinerAddress = &utils.StringModuleConfigParam{Name: "minerAddress"}

	//configParamBlockProducingInterval 同一时间片内生产一个区块后等待多久再生产下一个区块（单位：ms）
	configParamBlockProducingInterval = &utils.IntModuleConfigParam{Name: "producingInterval", DefaultValue: 100}

	//configParamMinPendingTxFetchInterval 出块时，从交易缓冲池中获取交易的重试时间间隔（找不到时）
	configParamMinPendingTxFetchInterval = &utils.IntModuleConfigParam{Name: "minPendingTxFetchInterval", DefaultValue: 100}

	//configParamMinCheckBlockExecResultFetchInterval 出块时，获取验证块执行结果重试时间间隔（找不到时）
	configParamMinCheckBlockExecResultFetchInterval = &utils.IntModuleConfigParam{Name: "minCheckBlockExecResultFetchInterval", DefaultValue: 100}
)

// NewMiner 新建一个Miner模块
func NewMiner(client *common.BaseClient) *SoloMiner {
	m := &SoloMiner{
		baseClient: client,
	}
	return m
}

// SoloMiner Miner
type SoloMiner struct {
	//矿工帐号：用于对区块和区块背书结果进行签名
	MinerAccount keystore.Account

	producer *Producer

	ShutdownWg sync.WaitGroup

	baseClient *common.BaseClient

	RootCtx context.Context

	Log logging.Log
}

// ProduceBlocks 生产块
func (s *SoloMiner) ProduceBlocks() {
	go s.producer.Run()
}

// Start 启动SoloMiner
func (s *SoloMiner) Start(rootCtx context.Context, cfg *commconfig.ModuleConfig, log logging.Log) {
	s.RootCtx, _ = context.WithCancel(rootCtx)
	s.Log = log

	minerAddress := configParamMinerAddress.GetValue(cfg, s.Log)
	if len(minerAddress) == 0 {
		s.Log.Error("address of miner not configured")
		return
	}
	//solo模式下，minerAddress必须与创世块中配置的一致
	consensusPayload := s.baseClient.GetCurParam().GetConsensus().GetSoloPayload()
	genMinerAddress := consensusPayload.GetAddress()
	if minerAddress != genMinerAddress {
		s.Log.Errorf("miner address '%s' is different with %s in genesis block", minerAddress, genMinerAddress)
		return
	}

	ks := keystore.GetKeyStore()
	s.MinerAccount = ks.GetAccount(protos.Address(minerAddress))
	if s.MinerAccount == nil {
		s.Log.Errorf("account '%s' not found in keystore", minerAddress)
		return
	}

	// 初始化区块生产者
	producerCtx, _ := context.WithCancel(s.RootCtx)
	s.producer = &Producer{
		module: s,
		ctx:    producerCtx,
	}

	producerAdapterConf := &types.ProducerConfig{
		ModuleLog:                            s.Log,
		MinerAccount:                         s.MinerAccount,
		ProducingInterval:                    configParamBlockProducingInterval.GetValue(cfg, s.Log),
		MinPendingTxFetchInterval:            configParamMinPendingTxFetchInterval.GetValue(cfg, s.Log),
		MinCheckBlockExecResultFetchInterval: configParamMinCheckBlockExecResultFetchInterval.GetValue(cfg, s.Log),
	}
	err := s.producer.init(producerAdapterConf)
	if err != nil {
		s.Log.Errorf("init producer failed, err: %s", err.Error())
		return
	}

	s.ProduceBlocks()
}
